17.5 清理

与复杂的标记过程不同,清理操作要简单得多。此时,所有未被标记的白色对象都不再被引用,可简单地将其内存回收。

mgc.go

func gcSweep(mode int) { // 设置work.spans=h_allspans // 还记得FixAlloc里面的recordspan么? gcCopySpans()

// 更新代龄 mheap_.sweepgen+=2 mheap_.sweepdone=0 sweep.spanidx=0

// 阻塞模式 if!_ConcurrentSweep||mode==gcForceBlockMode{ for sweepone() != ^uintptr(0) { sweep.npausesweep++ }

return

}

// 并发模式 if sweep.parked{ sweep.parked=false ready(sweep.g,0) } }

并发清理同样由一个专门的goroutine完成,它在runtime.main调用gcenable时被创建。

mgc.go

func gcenable() { c:=make(chan int,1) go bgsweep(c) c memstats.enablegc=true //now that runtime is initialized,GC is okay }

并发清理本质上就是一个死循环,被唤醒后开始执行清理任务。通过遍历所有span对象,触发内存分配器的回收操作。任务完成后,再次休眠,等待下次任务。

mgcsweep.go

var sweep sweepdata

// 并发清理状态 type sweepdata struct{ g *g parked bool }

func bgsweep(c chan int) { // 当前goroutine sweep.g=getg() sweep.parked=true

// 让gcenable退出 c1

// 休眠,等待gcSweep唤醒 goparkunlock(&sweep.lock, “GC sweep wait”,traceEvGoBlock,1)

for{ // 循环清理所有span for gosweepone() != ^uintptr(0) { // 并发调度,避免长时间占用CPU Gosched() }

if!gosweepdone() { 
    continue
 } 

 // 清理结束,休眠直到再次被唤醒 
sweep.parked=true
goparkunlock(&sweep.lock, "GC sweep wait",traceEvGoBlock,1) 

} }

func sweepone()uintptr{ g :=getg() sg:=mheap_.sweepgen

for{ // 从0开始的work.spans(h_allspans) 索引号 idx:=xadd(&sweep.spanidx,1) -1

 // 全部完成 
if idx>=uint32(len(work.spans)) { 
    mheap_.sweepdone=1
    return^uintptr(0) 
 } 

s:=work.spans[idx] 

 // 跳过闲置的span,直接更新代龄 
if s.state!=mSpanInUse{ 
    s.sweepgen=sg
    continue
 } 

 // 跳过已经或者正在被清理的span
if s.sweepgen!=sg-2|| !cas(&s.sweepgen,sg-2,sg-1) { 
    continue
 } 

 // 调用内存分配器回收方法 
npages:=s.npages
if!mSpan_Sweep(s,false) { 
    npages=0
 } 

return npages

} }

内存回收操作mSpan_Sweep,请参考第16章的相关章节。